/*
* UDPdriver.cpp
* Author: Zhong Ming
* Data: 20171016
*/


#include <assert.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include "include/UDPdriver.h"

struct CUDPdriver::CUDPdriver_priv {
    enum {
        udp_listen_mode,
        udp_connect_mode,
        udp_idle_mode
    } current_mode;
    struct sockaddr_in local_fd;
    struct sockaddr_in remote_fd;
    bool isRemoteConnected;
    int udp_fd = socket(AF_INET, SOCK_STREAM, 0);
};


CUDPdriver::CUDPdriver(void) {
    priv = new CUDPdriver_priv;
    priv->isRemoteConnected = false;
    priv->current_mode = CUDPdriver_priv::udp_idle_mode;

    priv->udp_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (priv->udp_fd < 0) {
        perror("UDP Socket Create Failed");
    }


    /*****************************************************************************/
    // 2017 11 03  ������
    int err = -1;
    int snd_size = 0;    /* ���ͻ�������С */
    int rcv_size = 0;    /* ���ջ�������С */
    int optlen;            /* ѡ��ֵ���� */

    /*
    * ���÷��ͻ�������С
    */
    snd_size = 16 * 1024 * 1024; /* ���ͻ�������СΪ8K */
    optlen = sizeof(snd_size);
    err = setsockopt(priv->udp_fd, SOL_SOCKET, SO_SNDBUF, (char *) &snd_size, optlen);
    if (err < 0) {
        printf("设置发送缓冲区大小错误\n");
    }

    /*
    * ���ý��ջ�������С
    */
    rcv_size = 1024 * 1024 * 16; /* ���ջ�������СΪ8K */
    optlen = sizeof(rcv_size);
    err = setsockopt(priv->udp_fd, SOL_SOCKET, SO_RCVBUF, (char *) &rcv_size, optlen);
    if (err < 0) {
        printf("设置接收缓冲区大小错误\n");
    }

    /*
    * �ȶ�ȡ���������õ����
    * ���ԭʼ���ͻ�������С
    */
    socklen_t optlen_send = sizeof(snd_size);
    err = getsockopt(priv->udp_fd, SOL_SOCKET, SO_SNDBUF, (char *) &snd_size, &optlen_send);
    if (err < 0) {
        printf("获取发送缓冲区大小错误\n");
    }
    /*
    * ��ӡԭʼ�������������
    */

    /*
    * ���ԭʼ���ջ�������С
    */
    socklen_t optlen_recv = sizeof(rcv_size);
    err = getsockopt(priv->udp_fd, SOL_SOCKET, SO_RCVBUF, (char *) &rcv_size, &optlen_recv);
    if (err < 0) {
        printf("设置接收缓冲区大小错误\n");
    }

    printf(" 发送缓冲区原始大小为: %d 字节\n", snd_size);
    printf(" 接收缓冲区原始大小为: %d 字节\n", rcv_size);
    //******************************************************************************/

}

CUDPdriver::~CUDPdriver(void) {
    if (priv->current_mode == CUDPdriver_priv::udp_connect_mode) {
        close(priv->udp_fd);
    } else if (priv->current_mode == CUDPdriver_priv::udp_listen_mode) {
        close(priv->udp_fd);
    }
    delete priv;

}

int CUDPdriver::listen(char *remote_ip, unsigned int local_port_input, void *options_input /*= NULL*/ ) {
    priv->local_fd.sin_family = AF_INET;
    priv->local_fd.sin_port = htons(local_port_input);
    priv->local_fd.sin_addr.s_addr = inet_addr(remote_ip);

    int ret = bind(priv->udp_fd, (struct sockaddr *) &priv->local_fd, sizeof(priv->local_fd));
    if (ret < 0) {
        perror("UDP Listen Bind Failed");
    }

    return ret;
}

int CUDPdriver::connect(char *remote_ip_input, unsigned int remote_port_input, unsigned int local_port_input,
                        void *options_input /*= NULL*/ ) {
    int ret = 0;

    priv->remote_fd.sin_family = AF_INET;
    priv->remote_fd.sin_port = htons(remote_port_input);
    priv->remote_fd.sin_addr.s_addr = inet_addr(remote_ip_input);
    priv->isRemoteConnected = true;

    priv->local_fd.sin_family = AF_INET;
    priv->local_fd.sin_port = htons(local_port_input);
    priv->local_fd.sin_addr.s_addr = INADDR_ANY;

    ret = bind(priv->udp_fd, (struct sockaddr *) &priv->local_fd, sizeof(priv->local_fd));
    if (ret < 0) {
        perror("UDP Listen Bind Failed");
    }

    return ret;
}

int CUDPdriver::connect(char *remote_ip_input, unsigned int remote_port_input, void *options_input /*= NULL*/ ) {
    int ret;

    ret = connect(remote_ip_input, remote_port_input, 0, options_input);

    return ret;
}

int CUDPdriver::send_data(void *buf, unsigned int bytes) {
    int ret;

    if (priv->isRemoteConnected == false) {
        printf("Error: Remote port is not specified!\n");
        return -1;
    }

    ret = sendto(priv->udp_fd, (const char *) buf, bytes, 0, (struct sockaddr *) &priv->remote_fd,
                 sizeof(struct sockaddr));
    if (ret < 0) {
        perror("sendto()");
    }
    return ret;
}

int CUDPdriver::send_data_with_ip(void *buf, unsigned int bytes, char *remote_ip, unsigned int remote_port) {
    int ret;
    struct sockaddr_in tmp_remote_fd;

    if (priv->isRemoteConnected == false) {
        printf("Error: Remote port is not specified!\n");
        return -1;
    }

    tmp_remote_fd.sin_family = AF_INET;
    tmp_remote_fd.sin_port = htons(remote_port);
    tmp_remote_fd.sin_addr.s_addr = INADDR_ANY;

    ret = sendto(priv->udp_fd, (const char *) buf, bytes, 0, (struct sockaddr *) &tmp_remote_fd,
                 sizeof(struct sockaddr));

    return ret;
}

int CUDPdriver::recv_data(void *buffer, unsigned int bytes) {
    struct sockaddr_in remote_info;
    socklen_t length = sizeof(sockaddr_in);
    int ret;
    memset(&remote_info, 0x00, length);
    ret = recvfrom(priv->udp_fd, (char *) buffer, bytes, 0, (struct sockaddr *) &remote_info, &length);

    if (ret > 0) {
        priv->remote_fd.sin_family = AF_INET;
        priv->remote_fd.sin_port = remote_info.sin_port;
        priv->remote_fd.sin_addr = remote_info.sin_addr;
        priv->isRemoteConnected = true;
    } else {
        perror("UDP Recv Failed");
    }

    return ret;
}

int CUDPdriver::recv_data_with_ip(UDPdriver_Data *data) {
    struct sockaddr_in remote_info;
    socklen_t length = sizeof(sockaddr_in);
    int ret;
    memset(&remote_info, 0x00, length);
    ret = recvfrom(priv->udp_fd, (char *) data->buffer, data->bytes, 0, (struct sockaddr *) &remote_info, &length);

    if (ret > 0) {
        priv->remote_fd.sin_family = AF_INET;
        priv->remote_fd.sin_port = remote_info.sin_port;
        priv->remote_fd.sin_addr = remote_info.sin_addr;
        priv->isRemoteConnected = true;

        strcpy(data->remote_ip, inet_ntoa(remote_info.sin_addr));
        strcpy(data->local_ip, inet_ntoa(priv->local_fd.sin_addr));
        data->remote_port = htons(remote_info.sin_port);
        data->local_port = htons(priv->local_fd.sin_port);
    } else {
        perror("UDP Recv Failed");
    }

    return ret;
}

